package me.gavin.base

import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.databinding.DataBindingUtil
import androidx.databinding.ViewDataBinding
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.StaggeredGridLayoutManager

/*
 * recyclerview adapter 多类型
 *
 * adapter.register(object : ItemViewDelegate<String, LayoutGavinBinding>() {
 *     override val layoutId = R.layout.layout_gavin
 *     override fun onBind(holder: RecyclerHolder<LayoutGavinBinding>, t: String) {
 *         // do something
 *     }
 * })
 *
 * [https://juejin.im/post/5c8f01f8e51d456bcb58d3af]
 * [https://github.com/drakeet/MultiType/blob/master/library/src/main/kotlin/com/drakeet/multitype/MultiTypeAdapter.kt]
 */
open class RecyclerMultiAdapter : RecyclerView.Adapter<RecyclerHolder<*>>() {

    val list = ArrayList<Any>()
    val types = ArrayList<Type<*>>()

    @Suppress("UNCHECKED_CAST")
    fun <T> getType(index: Int) = types[index] as Type<T>

    override fun getItemViewType(position: Int): Int {
        val item = list[position]
        val index = types.indexOfFirst {
            it.clazz == item::class.java
        }.takeOrElse({ it != -1 }) {
            types.indexOfFirst { it.clazz.isAssignableFrom(item::class.java) }
        }.takeOrElse({ it != -1 }) {
            error("Have you registered the ${item.javaClass.name} delegate?")
        }
        return index + getType<Any>(index).linker(item)
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerHolder<*> {
        return types[viewType].delegate.onCreateViewHolder(parent)
    }

    override fun onBindViewHolder(holder: RecyclerHolder<*>, position: Int) {
        types[holder.itemViewType].delegate.onBindViewHolder(holder, list[position])
    }

    override fun onAttachedToRecyclerView(recyclerView: RecyclerView) {
        super.onAttachedToRecyclerView(recyclerView)
        val layoutManager = recyclerView.layoutManager
        if (layoutManager is GridLayoutManager) {
            layoutManager.spanSizeLookup = object : GridLayoutManager.SpanSizeLookup() {
                override fun getSpanSize(position: Int): Int {
                    return if (types[getItemViewType(position)].delegate.isFullSpan) layoutManager.spanCount else 1
                }
            }
        }
    }

    override fun onViewAttachedToWindow(holder: RecyclerHolder<*>) {
        super.onViewAttachedToWindow(holder)
        val lp = holder.itemView.layoutParams ?: return
        if (lp is StaggeredGridLayoutManager.LayoutParams) {
            lp.isFullSpan = lp.isFullSpan || types[getItemViewType(holder.layoutPosition)].delegate.isFullSpan
        }/* else if (lp.javaClass == RecyclerView.LayoutParams::class.java) {
            if (types[getItemViewType(holder.layoutPosition)].isFullSpan) {
                lp.width = RecyclerView.LayoutParams.MATCH_PARENT
            }
        }*/
    }

    override fun getItemCount() = list.size

    inline fun <reified T> register(delegate: ItemViewDelegate<T, *>) {
        types += Type(T::class.java, delegate)
    }

    inline fun <reified T> register(
        vararg delegates: ItemViewDelegate<T, *>,
        noinline linker: ((T) -> Int)
    ) {
        for (delegate in delegates) {
            types += Type(T::class.java, delegate, linker)
        }
    }

}

class Type<T>(
    val clazz: Class<T>,
    val delegate: ItemViewDelegate<T, *>,
    val linker: ((T) -> Int) = { 0 }
)

abstract class ItemViewDelegate<T, B : ViewDataBinding>(val isFullSpan: Boolean = false) {
    abstract val layoutId: Int
    fun onCreateViewHolder(parent: ViewGroup): RecyclerHolder<B> {
        return DataBindingUtil
            .inflate<B>(LayoutInflater.from(parent.context), layoutId, parent, false)
            .let { RecyclerHolder(it) }
    }

    @Suppress("UNCHECKED_CAST")
    fun onBindViewHolder(holder: RecyclerHolder<*>, t: Any) {
        onBind(holder as RecyclerHolder<B>, t as T)
    }

    abstract fun onBind(holder: RecyclerHolder<B>, t: T)
}
